package com.linkgie.galaxyframework.httpproxy;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import com.linkgie.galaxyframework.httpproxy.annotations.HttpRequest;
import com.linkgie.galaxyframework.httpproxy.annotations.PathParam;
import com.linkgie.galaxyframework.httpproxy.annotations.RequestBody;
import com.linkgie.galaxyframework.httpproxy.annotations.RequestParam;
import com.linkgie.galaxyframework.httpproxy.annotations.RequestParamList;
import com.linkgie.galaxyframework.httpproxy.converters.ObjectToStringConverter;

/**
 * HTTP 服务方法；
 * 
 * @author haiq
 *
 */
public class HttpRequestDefinition {

	/**
	 * 请求路径；
	 * 
	 * 默认为空；
	 * 
	 * 此时按照 service 的 path 属性+方法名 产生最终的请求路径；
	 * 
	 * @return
	 */
	private String path;

	/**
	 * HTTP 请求方法;
	 * 
	 * @return
	 */
	private HttpMethod httpMethod;

	/**
	 * HTTP 的 Content-Type;
	 * 
	 * @return
	 */
	private String contentType;

	/**
	 * 自定义的返回值转换器；必须实现 {@link RequestParamFilter} 接口；
	 * 
	 * @return
	 */
	private Class<? extends RequestParamFilter> requestParamFilter = RequestParamFilter.class;

	/**
	 * 自定义的返回值转换器；必须实现 {@link ResponseConverter} 接口；
	 * 
	 * @return
	 */
	private Class<? extends ResponseConverter> responseConverter = ResponseConverter.class;

	/**
	 * 自定义的返回值处理器；必须实现 {@link ResponseHandler} 接口；
	 *
	 * @return
	 */
	private Class<? extends ResponseHandler> responseHandler = ResponseHandler.class;

	/**
	 * 当检测到 http 错误时是否在引发的 {@link HttpStatusException} 中包含回复的内容；
	 * 
	 * 默认为 false；
	 * 
	 * @return
	 */
	private boolean resolveContentOnHttpError = false;

	/**
	 * 请求发起和回复处理过程中，直接对外抛出的异常类型；
	 */
	private Class<?>[] thrownExceptionTypes;

	/**
	 * 服务方法；
	 *
	 * @return
	 */
	private Method serviceMethod;

	private Class<?> responseType;

	// 注：HttpProxy 的实现支持在参数定义多个 RequestBody ，此时，多个 RequestBody 的数据会按照参数顺序地写入输出流；
	// 通过这种方式，可以实现连续多个数据对象的写入；
	// private boolean hasBody = false;

	private List<InputParam> params;

	/**
	 * @return the path
	 */
	public String getPath() {
		return path;
	}

	/**
	 * @param path the path to set
	 */
	public void setPath(String path) {
		this.path = path;
	}

	/**
	 * @return the method
	 */
	public HttpMethod getHttpMethod() {
		return httpMethod;
	}

	/**
	 * @param method the method to set
	 */
	public void setHttpMethod(HttpMethod method) {
		this.httpMethod = method;
	}

	/**
	 * @return the contentType
	 */
	public String getContentType() {
		return contentType;
	}

	/**
	 * @param contentType the contentType to set
	 */
	public void setContentType(String contentType) {
		this.contentType = contentType;
	}

	/**
	 * @return the requestParamFilter
	 */
	public Class<? extends RequestParamFilter> getRequestParamFilter() {
		return requestParamFilter;
	}

	/**
	 * @param requestParamFilter the requestParamFilter to set
	 */
	public void setRequestParamFilter(Class<? extends RequestParamFilter> requestParamFilter) {
		this.requestParamFilter = requestParamFilter;
	}

	/**
	 * @return the responseConverter
	 */
	public Class<? extends ResponseConverter> getResponseConverter() {
		return responseConverter;
	}

	/**
	 * @param responseConverter the responseConverter to set
	 */
	public void setResponseConverter(Class<? extends ResponseConverter> responseConverter) {
		this.responseConverter = responseConverter;
	}

	/**
	 * @return the responseHandler
	 */
	public Class<? extends ResponseHandler> getResponseHandler() {
		return responseHandler;
	}

	/**
	 * @param responseHandler the responseHandler to set
	 */
	public void setResponseHandler(Class<? extends ResponseHandler> responseHandler) {
		this.responseHandler = responseHandler;
	}

	/**
	 * @return the resolveContentOnHttpError
	 */
	public boolean isResolveContentOnHttpError() {
		return resolveContentOnHttpError;
	}

	/**
	 * @param resolveContentOnHttpError the resolveContentOnHttpError to set
	 */
	public void setResolveContentOnHttpError(boolean resolveContentOnHttpError) {
		this.resolveContentOnHttpError = resolveContentOnHttpError;
	}

	/**
	 * 关联的服务接口的方法；
	 * <p>
	 * 如果 HTTP 请求不是通过 {@link ServiceProxy} 标注接口的方法产生的，则此返回 null；
	 * 
	 * @return the serviceMethod
	 */
	public Method getServiceMethod() {
		return serviceMethod;
	}

	public Class<?>[] getThrownExceptionTypes() {
		return this.thrownExceptionTypes;
	}

	/**
	 * @param thrownExceptionTypes the thrownExceptionTypes to set
	 */
	public void setThrownExceptionTypes(Class<?>[] thrownExceptionTypes) {
		this.thrownExceptionTypes = thrownExceptionTypes;
	}

	public Class<?> getResponseType() {
		return this.responseType;
	}

	/**
	 * @param responseType the responseType to set
	 */
	public void setResponseType(Class<?> responseType) {
		this.responseType = responseType;
	}

	/**
	 * @return the params
	 */
	public List<InputParam> getParams() {
		return params == null ? Collections.emptyList() : Collections.unmodifiableList(params);
	}

	private synchronized void innerAddParam(InputParam param) {
		if (this.params == null) {
			this.params = new LinkedList<InputParam>();
		}
		this.params.add(param);
	}

	public void addParam(Class<?> dataType, RequestBody reqBodyAnno) {
		Body body = new Body(dataType, reqBodyAnno);
		addParam(dataType, body);
	}

	public synchronized void addParam(Class<?> dataType, Body body) {
		// 注：HttpProxy 的实现支持在参数定义多个 RequestBody ，此时，多个 RequestBody 的数据会按照参数顺序地写入输出流；
		// 通过这种方式，可以实现连续多个数据对象的写入；
		// if (this.hasBody == true) {
		// throw new IllegalStateException("Http request body has already been
		// defined!");
		// }

		innerAddParam(body);

		// this.hasBody = true;
	}

	public void addParam(Class<?> dataType, PathParam pathVariableAnno) {
		PathVariable pathVariable = new PathVariable(dataType, pathVariableAnno);
		addParam(dataType, pathVariable);
	}

	public void addParam(Class<?> dataType, PathVariable pathVariable) {
		innerAddParam(pathVariable);
	}

	public void addParam(Class<?> dataType, RequestParamList paramAnno) {
		ListParam listParam = new ListParam(dataType, paramAnno);
		addParam(listParam);
	}

	public void addParam(ListParam listParam) {
		innerAddParam(listParam);
	}

	public void addParam(Class<?> dataType, RequestParam paramAnno) {
		Param param = new Param(dataType, paramAnno);
		addParam(param);
	}

	public void addParam(Param param) {
		innerAddParam(param);
	}

	public HttpRequestDefinition() {
	}

	public HttpRequestDefinition(Method serviceMethod, HttpRequest requestAnnotation) {
		this.serviceMethod = serviceMethod;
		this.responseType = serviceMethod.getReturnType();
		this.thrownExceptionTypes = serviceMethod.getExceptionTypes();

		this.path = requestAnnotation.path();
		this.httpMethod = requestAnnotation.method();
		this.contentType = requestAnnotation.contentType();
		this.requestParamFilter = requestAnnotation.requestParamFilter();
		this.responseConverter = requestAnnotation.responseConverter();
		this.responseHandler = requestAnnotation.responseHandler();
		this.resolveContentOnHttpError = requestAnnotation.resolveContentOnHttpError();
	}

	// ---------------------- 内部类型 ----------------------

	/**
	 * 请求的传入参数；
	 */
	public static abstract class InputParam {

		private Class<?> paramTypes;

		public InputParam(Class<?> paramTypes) {
			this.paramTypes = paramTypes;
		}

		/**
		 * @return the paramTypes
		 */
		public Class<?> getParamTypes() {
			return paramTypes;
		}

	}

	/**
	 * 请求主体定义；
	 */
	public static class Body extends InputParam {

		private boolean required = true;

		/**
		 * 参数值转换器的类型；
		 * 
		 * 指定的参数值转换器必须实现 RequestBodyConverter 接口；
		 * 
		 * 如果未指定，
		 * 
		 * 对于 InputStream 或 byte 数组类型，则直接输出字节内容；
		 * 
		 * 对于除此之外的其它类型，则通过 Object.toString() 方法获取文本值按照指定的编码；
		 * 
		 * @return
		 */
		private Class<?> converter = RequestBodyConverter.class;

		/**
		 * @return the required
		 */
		public boolean isRequired() {
			return required;
		}

		/**
		 * @param required the required to set
		 */
		public void setRequired(boolean required) {
			this.required = required;
		}

		/**
		 * @return the converter
		 */
		public Class<?> getConverter() {
			return converter;
		}

		/**
		 * @param converter the converter to set
		 */
		public void setConverter(Class<?> converter) {
			this.converter = converter;
		}

		Body(Class<?> dataType) {
			super(dataType);
		}

		Body(Class<?> dataType, RequestBody reqBodyAnno) {
			super(dataType);
			this.required = reqBodyAnno.required();
			this.converter = reqBodyAnno.converter();
		}

	}

	/**
	 * 路径变量定义；
	 */
	public static class PathVariable extends InputParam {

		/**
		 * 路径中的变量名；
		 * 
		 * @return
		 */
		private String name;

		/**
		 * 参数值转换器的类型；
		 * 
		 * 指定的参数值转换器必须实现 StringConverter 接口；
		 * 
		 * 如果未指定，则默认通过 toString() 方法获取参数最终的文本值；
		 * 
		 * @return
		 */
		private Class<?> converter = ObjectToStringConverter.class;

		/**
		 * @return the name
		 */
		public String getName() {
			return name;
		}

		/**
		 * @param name the name to set
		 */
		public void setName(String name) {
			this.name = name;
		}

		/**
		 * @return the converter
		 */
		public Class<?> getConverter() {
			return converter;
		}

		/**
		 * @param converter the converter to set
		 */
		public void setConverter(Class<?> converter) {
			this.converter = converter;
		}

		PathVariable(Class<?> dataType) {
			super(dataType);
		}

		PathVariable(Class<?> dataType, PathParam pathParamAnno) {
			super(dataType);
			this.name = pathParamAnno.name();
			this.converter = pathParamAnno.converter();
		}
	}

	/**
	 * 参数列表定义；
	 */
	public static class ListParam extends InputParam {

		/**
		 * 属性前缀；
		 * 
		 * 当一个方法中通过 RequestParams 定义了多个参数表时，可通过定义不同的前缀以防止属性名称冲突；
		 * 
		 * @return
		 */
		private String prefix = "";

		/**
		 * 属性分隔符；
		 * 
		 * 最终的参数名由前缀+分隔符+原始参数名构成；
		 * 
		 * @return
		 */
		private String seperator = ".";

		/**
		 * 参数是否必须提供；
		 * 
		 * 运行时如果必须的参数为 null 将引发异常；
		 * 
		 * 默认为 true；
		 * 
		 * @return
		 */
		private boolean required = true;

		/**
		 * 自定义的转换器；
		 * 
		 * 必须实现 {@link PropertiesConverter} 接口；
		 * 
		 * @return
		 */
		private Class<? extends PropertiesConverter> converter = PropertiesConverter.class;

		/**
		 * @return the prefix
		 */
		public String getPrefix() {
			return prefix;
		}

		/**
		 * @param prefix the prefix to set
		 */
		public void setPrefix(String prefix) {
			this.prefix = prefix;
		}

		/**
		 * @return the seperator
		 */
		public String getSeperator() {
			return seperator;
		}

		/**
		 * @param seperator the seperator to set
		 */
		public void setSeperator(String seperator) {
			this.seperator = seperator;
		}

		/**
		 * @return the required
		 */
		public boolean isRequired() {
			return required;
		}

		/**
		 * @param required the required to set
		 */
		public void setRequired(boolean required) {
			this.required = required;
		}

		/**
		 * @return the converter
		 */
		public Class<? extends PropertiesConverter> getConverter() {
			return converter;
		}

		/**
		 * @param converter the converter to set
		 */
		public void setConverter(Class<? extends PropertiesConverter> converter) {
			this.converter = converter;
		}

		ListParam(Class<?> dataType) {
			super(dataType);
		}

		ListParam(Class<?> dataType, RequestParamList reqParamAnno) {
			super(dataType);
			this.prefix = reqParamAnno.prefix();
			this.seperator = reqParamAnno.seperator();
			this.required = reqParamAnno.required();
			this.converter = reqParamAnno.converter();
		}

	}

	/**
	 * 单个参数定义；
	 */
	public static class Param extends InputParam {
		/**
		 * 参数名；
		 * 
		 * @return
		 */
		private String name;

		/**
		 * 参数是否必须提供；
		 * <p>
		 * 
		 * 如果必须的参数的值为 null ，将引发异常；
		 * <p>
		 * 
		 * 如果非必须的参数的值为 null，则忽略此参数；
		 * <p>
		 * 
		 * 默认为 true；
		 * 
		 * @return
		 */
		private boolean required = true;

		/**
		 * 是否为多项参数；
		 * 
		 * <br>
		 * 
		 * 如果设为 true，则参数必须为数组类型，此参数将输出为“列表参数”；
		 * 
		 * @return
		 */
		private boolean iterable = false;

		/**
		 * 忽略值；
		 * 
		 * 仅当 require 为 false 时有效，指示当参数的值为 null 或者与 ignoreValue 相等时将忽略参数；
		 * 
		 * @return
		 */
		private String ignoreValue = "";

		/**
		 * 参数值转换器的类型；
		 * 
		 * 指定的参数值转换器必须实现 StringConverter 接口；
		 * 
		 * 如果未指定，则默认通过 toString() 方法获取参数最终的文本值；
		 * 
		 * @return
		 */
		private Class<?> converter = ObjectToStringConverter.class;

		/**
		 * @return the name
		 */
		public String getName() {
			return name;
		}

		/**
		 * @param name the name to set
		 */
		public void setName(String name) {
			this.name = name;
		}

		/**
		 * @return the required
		 */
		public boolean isRequired() {
			return required;
		}

		/**
		 * @param required the required to set
		 */
		public void setRequired(boolean required) {
			this.required = required;
		}

		/**
		 * @return the iterable
		 */
		public boolean isIterable() {
			return iterable;
		}

		/**
		 * @param iterable the iterable to set
		 */
		public void setIterable(boolean iterable) {
			this.iterable = iterable;
		}

		/**
		 * @return the ignoreValue
		 */
		public String getIgnoreValue() {
			return ignoreValue;
		}

		/**
		 * @param ignoreValue the ignoreValue to set
		 */
		public void setIgnoreValue(String ignoreValue) {
			this.ignoreValue = ignoreValue;
		}

		/**
		 * @return the converter
		 */
		public Class<?> getConverter() {
			return converter;
		}

		/**
		 * @param converter the converter to set
		 */
		public void setConverter(Class<?> converter) {
			this.converter = converter;
		}

		Param(Class<?> dataType) {
			super(dataType);
		}

		Param(Class<?> dataType, RequestParam reqParamAnno) {
			super(dataType);
			this.name = reqParamAnno.name();
			this.required = reqParamAnno.required();
			this.iterable = reqParamAnno.iterable();
			this.ignoreValue = reqParamAnno.ignoreValue();
			this.converter = reqParamAnno.converter();
		}
	}

}
